﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;

namespace FerrSimplicity
{
    /// <summary>
    /// This class is a singleton that manages the 2D cameras in the scene. If there are
    /// no cameras in the scene, it provides some basic, default camera properties
    /// </summary>
    public class ManagerCameras
    {
        #region Singleton
        private static ManagerCameras mInstance = null;
        public  static ManagerCameras Instance
        {
            get { if (mInstance == null) mInstance = new ManagerCameras(); return mInstance; }
        }
        private ManagerCameras() { mCameras = new List<ComCamera>(); }
        #endregion

        #region Fields
        private List<ComCamera> mCameras;
        private ComCamera       mActiveCamera;
        #endregion

        #region Properties
        /// <summary>
        /// Using this is far preferable to referencing the CameraObject, as this property will provide default
        /// values if no active camera is present.
        /// </summary>
        public float          Scale
        {
            get { if (mActiveCamera == null) return 1f; return mActiveCamera.Parent.Transform.ScaleSc; }
            set { if (mActiveCamera != null) mActiveCamera.Parent.Transform.ScaleSc = value; }
        }
        /// <summary>
        /// The transformation matrix for the current active camera. It negates the position to get the camera
        /// transform, and negates it again when finished. If there is no camera, Matrix.Identity is returned
        /// </summary>
        public Matrix         Transform
        {
            get
            {
                Matrix ret = Matrix.Identity;

                if (mActiveCamera != null)
                {
                    Vector2 middle = new Vector2(FerrGlobals.SpriteBatch.GraphicsDevice.Viewport.Width / 2, FerrGlobals.SpriteBatch.GraphicsDevice.Viewport.Height / 2);
                    middle = middle / mActiveCamera.Parent.Transform.ScaleSc;

                    ret = Matrix.CreateTranslation(-(mActiveCamera.Parent.Transform.Position.X - middle.X), -(mActiveCamera.Parent.Transform.Position.Y - middle.Y) , 0) * 
                          Matrix.CreateScale      (  mActiveCamera.Parent.Transform.Scale   .X,               mActiveCamera.Parent.Transform.Scale   .Y, mActiveCamera.Parent.Transform.Scale.Z);

                    /*
                    mActiveCamera.Parent.Transform.Position2D -= middle;

                    mActiveCamera.Parent.Transform.Position = -mActiveCamera.Parent.Transform.Position;
                    ret = mActiveCamera.Parent.Transform.Transform;
                    mActiveCamera.Parent.Transform.Position = -mActiveCamera.Parent.Transform.Position;

                    mActiveCamera.Parent.Transform.Position2D += middle;*/
                }

                return ret;
            }
        }
        /// <summary>
        /// Returns a rectangle that describes the boundaries of the screen in world coordinates. This factors 
        /// in the camera scale as well as just the width and height of the window.
        /// </summary>
        public Rectangle      Bounds
        {
            get 
            {
                if (mActiveCamera == null)
                    return new Rectangle(0, 0, FerrGlobals.SpriteBatch.GraphicsDevice.Viewport.Width, FerrGlobals.SpriteBatch.GraphicsDevice.Viewport.Height);
                else
                {
                    Vector2 middle = new Vector2(FerrGlobals.SpriteBatch.GraphicsDevice.Viewport.Width / 2, FerrGlobals.SpriteBatch.GraphicsDevice.Viewport.Height / 2);
                    middle = middle / Scale;

                    return new Rectangle((int)(mActiveCamera.Parent.Transform.Position.X - middle.X), 
                                         (int)(mActiveCamera.Parent.Transform.Position.Y - middle.Y), 
                                         (int)(FerrGlobals.SpriteBatch.GraphicsDevice.Viewport.Width  * Scale),
                                         (int)(FerrGlobals.SpriteBatch.GraphicsDevice.Viewport.Height * Scale));
                }
            }
        }
        /// <summary>
        /// The game object that holds the active camera. This will be null if there is no active camera.
        /// </summary>
        public BaseGameObject CameraObject
        {
            get { if (mActiveCamera != null) return mActiveCamera.Parent; return null; }
        }
        #endregion

        #region Methods
        /// <summary>
        /// Registers a camera with the manager, probably should only be called from ComCamera. It
        /// automatically set it as active, if there is no currently active camera.
        /// </summary>
        /// <param name="aCamera">The camera component to register.</param>
        internal void      RegisterCamera  (ComCamera aCamera)
        {
            if (GetCamera(aCamera.Name) != null)
                throw new ArgumentException("A camera named '" + aCamera.Name + "' already exists!");

            if (mActiveCamera == null)
                mActiveCamera = aCamera;
            mCameras.Add(aCamera);
        }
        /// <summary>
        /// Unregisters the indicated camera component, probably should only be called from ComCamera.
        /// If it was the active camera, a new camera is found and set active.
        /// </summary>
        /// <param name="aCamera">The camera component to unregister</param>
        internal void      UnregisterCamera(ComCamera aCamera)
        {
            mCameras.Remove(aCamera);

            // if it was the active camera, we need to set up a new one
            if (mActiveCamera == aCamera)
            {
                mActiveCamera = null;
                if (mCameras.Count > 0)
                    mActiveCamera = mCameras[0];
            }
        }
        /// <summary>
        /// Finds a camera by the given name, and sets it active.
        /// </summary>
        /// <param name="aCameraName">The camera's name</param>
        /// <returns>True if the camera is found, false if not</returns>
        public   bool      SetActive       (string    aCameraName)
        {
            for (int i = 0; i < mCameras.Count; i++)
            {
                if (mCameras[i].Name == aCameraName)
                {
                    mActiveCamera = mCameras[i];
                    return true;
                }
            }
            return false;
        }
        /// <summary>
        /// Converts a location on the screen, such as a mouse location, into a world coordinate.
        /// </summary>
        /// <param name="aWorldPos">A location in screen space</param>
        /// <returns>A location in world space.</returns>
        public   Vector2   ScreenToWorld   (Vector2   aWorldPos)
        {
            if (mActiveCamera != null)
                return aWorldPos + mActiveCamera.Parent.Transform.Position2D;
            else
                return aWorldPos;
        }
        /// <summary>
        /// Finds a given camera by name. For loop lookup
        /// </summary>
        /// <param name="aCameraName">Name of the camera</param>
        /// <returns>Component of the given camera, null if none found</returns>
        private  ComCamera GetCamera       (string    aCameraName)
        {
            for (int i = 0; i < mCameras.Count; i++)
            {
                if (mCameras[i].Name == aCameraName)
                {
                    return mCameras[i];
                }
            }
            return null;
        }
        #endregion
    }
}
